#pragma once

#include "channel.h"
#include "containers.h"
#include "types.h"
#include <ctime>
#include <limits>
#include <memory.h>

namespace Trade {

struct ExtRegion
{
  dir_t dir = 0;
  bool top = false;
  bool btm = false;
  price_t hp_A = 0;
  price_t hp_V = 0;
  price_t hpx = 0;

  void reset() noexcept { memset((void*)(this), 0, sizeof(ExtRegion)); };
  dir_t large_reflection(const price_t hp) const noexcept;
  dir_t ext_dir(const price_t hpx) const noexcept;
  price_t get_top() const noexcept { return hpx - hp_A; };
  price_t get_btm() const noexcept { return hpx - hp_V; };
};

enum LIMIT_REACHED : uint8_t
{
  REACHED_NONE = 0,
  REACHED_UP_1 = 1,
  REACHED_UP_2 = 2,
  REACHED_DN_1 = 4,
  REACHED_DN_2 = 8,
  REACHED_TIMER_OUT = 16,
};

enum class LINE_REGION : uint8_t
{
  UNKNOW = 0,
  MIDDLE = 1,
  ABOVE_1 = 2,
  ABOVE_2 = 3,
  ABOVE_2_1 = 4,
  BELOW_1 = 5,
  BELOW_2 = 6,
  BELOW_2_1 = 7,
};

struct NearestDelta
{
  price_t up_1 = 0;
  price_t up_2 = 0;
  price_t dn_1 = 0;
  price_t dn_2 = 0;
  price_t up_1_pct = 0;
  price_t up_2_pct = 0;
  price_t dn_1_pct = 0;
  price_t dn_2_pct = 0;

  void reset() noexcept
  {
    up_1 = up_2 = dn_1 = dn_2 = up_1_pct = up_2_pct = dn_1_pct = dn_2_pct =
      std::numeric_limits<price_t>::max();
  }
};

struct LinesGodenPnt
{
  int nup = 0; // number of up-line
  int ndn = 0; // number of dn-line
  reached_t reached = 0;
  price_t gp_up1; // up1 to dn1
  price_t gp_up2; // up2 to dn2
  price_t gp_dn1; // dn1 to up1
  price_t gp_dn2; // dn2 to up2

  void reset() noexcept { memset((void*)(this), 0, sizeof(LinesGodenPnt)); }
  reached_t reached_only(const price_t hpx) noexcept;
};

struct Lines
{
  int8_t nup = 0;
  int8_t ndn = 0;
  uint8_t reached = 0; // hit
  price_t hpx = 0;
  price_t hp_up_1 = 0;
  price_t hp_up_2 = 0;
  price_t hp_dn_1 = 0;
  price_t hp_dn_2 = 0;
  NearestDelta nd;
  int64_t idx = 0;

  ExtRegion ext_rgn;
  // SmallGradePrice sgp;

  static constexpr price_t MINIMAL_LINE_CHANGE = 5;
  static constexpr price_t MINIMAL_GOOD_SPACE = 15;

  void reset() noexcept;
  void print(const int64_t idx, const price_t hpx) const noexcept;
  price_t main_space() const noexcept;
  bool good_main_space() const noexcept;
  bool between_the_lines(const price_t hpx, const dir_t d) noexcept;
  bool operator!=(const Lines& l) const noexcept;
  price_t get_max_min_line(const dir_t d) noexcept;
  price_t get_middle_line(const dir_t d) noexcept;

  reached_t limit_reached(const price_t hpx) noexcept;
  reached_t reached_only(const price_t hpx) const noexcept;

  price_t get_line1_space(const dir_t d, const price_t x) const noexcept;
  price_t get_opposite_line_1(const dir_t d, const price_t hpx) const noexcept;
  bool good_space_for_dir(const price_t x,
                          const dir_t d,
                          int8_t& n_target,
                          int8_t& x1_id,
                          int8_t& x2_id,
                          price_t& space,
                          price_t& target_x1,
                          price_t& target_x2) noexcept;
  price_t space_for_dir(const price_t x, const dir_t d) noexcept;

  bool is_ext_top_btm(const price_t y, const dir_t d) noexcept;
  int8_t amount(const dir_t d) noexcept;
  void copy(const Lines& l) noexcept;
  void copy(const Lines* l) noexcept;
  LINE_REGION region(const price_t hpx) noexcept;

  //
  // d > 0, update up12; d < 0, update dn12; d = update both.
  //
  int8_t update_lines(const Lines& l,
                      const dir_t d,
                      const price_t hpx) noexcept;
  void print_hited(const price_t hp_now, const int64_t idx) noexcept;
  uint8_t retrive_reached(const dir_t d) noexcept;
  uint8_t retrive_opposite_reached(const dir_t d) noexcept;
  uint8_t retrive_all_reached() noexcept;
  bool reached_opposite_line1(const dir_t d) noexcept;
  bool reached_opposite_line2(const dir_t d) noexcept;
  bool reached_line1(const dir_t d) noexcept;
  bool reached_line2(const dir_t d) noexcept;

  bool almost_reached_line2(const dir_t d) const noexcept;
  bool only_one_line(const dir_t d) const noexcept;
  price_t get_golden_point(const dir_t d) const noexcept;
  uint8_t ext_reached() noexcept;
  dir_t get_ext_hit_dir(const ExtRegion& ext_rgn) noexcept;

  bool only_one_line_and_large_space(const dir_t d,
                                     const price_t hpx) const noexcept;
  bool good_entry_region(const dir_t d, const price_t hpx) noexcept;
  bool good_line_region(const dir_t d, const price_t hpx) noexcept;

  price_t get_line1_price(const dir_t d) const noexcept;
  price_t get_line2_price(const dir_t d) const noexcept;
  void get_golden_points(LinesGodenPnt& lgp) noexcept;
  void clear_reached_sapce() noexcept;
  price_t get_dn_price() const noexcept;
  price_t get_up_price() const noexcept;
  int have_space_to_renew(const Lines& l) const noexcept;
};

extern bool
limit_reached_same_side(const dir_t d, const uint8_t reached);
extern bool
limit_reached_line2(const dir_t d, const uint8_t reached);
extern bool
limit_reached_line1(const dir_t d, const uint8_t reached);

extern uint8_t
idx_of_reached_line(const uint8_t reached);
extern void
print_reached(const char* name,
              const dir_t d,
              const uint8_t reached,
              const int64_t idx) noexcept;
extern bool
longer_than(const time_t tm, const time_t t_now, const int64_t delta) noexcept;
extern bool
less_than(const time_t tm, const time_t t_now, const int64_t delta) noexcept;

enum class PREDICT_LINE_TYPE : int8_t
{
  NONE = 0,
  U1 = 1,
  U2 = 2,
  D1 = -1,
  D2 = -2,
};

struct PredictLine
{
  PREDICT_LINE_TYPE plt = PREDICT_LINE_TYPE::NONE;
  reached_t reached = LIMIT_REACHED::REACHED_NONE;
  price_t hpx = 0;
  time_t ts = 0;

  bool operator==(const PredictLine& pl) const noexcept
  {
    return std::abs(hpx - pl.hpx) <= Lines::MINIMAL_LINE_CHANGE;
  }

  bool operator!=(const PredictLine& pl) const noexcept
  {
    return std::abs(hpx - pl.hpx) > Lines::MINIMAL_LINE_CHANGE;
  }

  friend std::ostream& operator<<(std::ostream& o,
                                  const PredictLine& pl) noexcept
  {
    o << "(" << int(pl.plt) << ", " << uint(pl.reached) << ", " << pl.hpx
      << ", " << pl.ts << ") ";
    return o;
  }
};

struct UpLine : public PredictLine
{
  bool operator<(const UpLine& pl) const noexcept
  {
    return ((std::abs(hpx - pl.hpx) > Lines::MINIMAL_LINE_CHANGE) &&
            hpx < pl.hpx);
  }
};

struct DnLine : public PredictLine
{
  bool operator<(const DnLine& pl) const noexcept
  {
    return ((std::abs(hpx - pl.hpx) > Lines::MINIMAL_LINE_CHANGE) &&
            hpx > pl.hpx);
  }
};

using UpLineSet = PointerSet<UpLine, 100>;
using DnLineSet = PointerSet<DnLine, 100>;

using PredictLineArray = PredictLine[10];
struct PredictLine10
{
  usize_t size = 0;
  PredictLineArray pla;
};

struct PriceTime
{
  price_t hp;
  time_t tm;
  PriceTime(const price_t hpx, const time_t ts) noexcept
    : hp(hpx)
    , tm(ts)
  {
  }
};

using Price2h = CircleVector<PriceTime, 2 * 60 * 60>;
struct MaxMin2h
{
  MaxMin mm;
  Price2h p2h;
  void new_data(const price_t hpx, const time_t tm) noexcept;
};

extern void
convert_line_set(const UpLineSet& ups,
                 const DnLineSet& dns,
                 PredictLine10& up10,
                 PredictLine10& dn10) noexcept;

} // namespace Trade
